Passed
Push — master ( 1e221c...ed4a4b )
by Apocist
47s
created

nodeVBulletinAPI.js ➔ getThread   A

Complexity

Conditions 1
Paths 12

Size

Total Lines 28
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 1
eloc 21
c 2
b 0
f 0
nc 12
nop 2
dl 0
loc 28
rs 9.376

1 Function

Rating   Name   Duplication   Size   Complexity  
A nodeVBulletinAPI.js ➔ ... ➔ ??? 0 22 4
1
'use strict';
2
const md5 = require('js-md5'),
3
    os = require('os'),
4
    request = require('request'),
5
    _ = require('underscore'),
6
    Forum = require('./Forum'),
7
    Inbox = require('./Inbox'),
8
    Member = require('./Member'),
9
    Message = require('./Message'),
10
    //Post = require('./Post'),
11
    Thread = require('./Thread'),
12
    {version} = require('./package.json');
13
14
/**
15
 *
16
 */
17
class VBApi {
18
    /**
19
     * Initialize a vb api connection .This needs to be called for the first time
20
     * @param {string} apiUrl
21
     * @param {string} apiKey
22
     * @param {string} platformName
23
     * @param {string} platformVersion
24
     * @param {object=} options - A fallback to the old style config
25
     * @param {string=} options.apiUrl
26
     * @param {string=} options.apiKey
27
     * @param {string=} options.platformName
28
     * @param {string=} options.platformVersion
29
     */
30
    constructor(apiUrl, apiKey, platformName, platformVersion, options) {
31
        this.defaultVars = {
32
            baseUrl: '', //Needed for cookie related commands
33
            apiUrl: '',
34
            apiKey: '',
35
            clientName: 'nodeVBulletinAPI',
36
            clientVersion: version,
37
            uniqueId: ''
38
        };
39
40
        this.clientSessionVars = {
41
            apiVersion: '',
42
            apiAccessToken: '',
43
            sessionHash: '', // Unused?
44
            apiClientId: '',
45
            secret: '',
46
            inited: false,
47
            error: null
48
        };
49
50
        /**
51
         * @typedef UserVars
52
         * @property {string} dbsessionhash
53
         * @property {number} userid
54
         * @property {string} username
55
         * @property {boolean} loggedIn
56
         * @type {UserVars}
57
         */
58
        this.userSessionVars = {
59
            dbsessionhash: '',
60
            username: '',
61
            userid: 0,
62
            loggedIn: false
63
        };
64
65
        /** @private */
66
        this.__waitingForInitializationCallback = function () {
67
        }; // A blank callback to be filled in
68
69
        options = options || {};
70
        options.apiUrl = apiUrl || options.apiUrl || '';
71
        options.apiKey = apiKey || options.apiKey || '';
72
        options.platformName = platformName || options.platformName || '';
73
        options.platformVersion = platformVersion || options.platformVersion || '';
74
75
        if (
76
            options.apiUrl !== ''
77
            && options.apiUrl !== ''
78
            && options.platformName !== ''
79
            && options.platformVersion !== ''
80
        ) {
81
            this.__initialize(options);
82
        } else {
83
            this.clientSessionVars.error = 'apiInit(): Initialization requires a `apiUrl`, `apiKey`, `platformName`, and `platformVersion`';
84
            this.__waitingForInitializationCallback(false);
85
        }
86
    }
87
88
    /**
89
     * Initialize a vb api connection. This needs to be called for the first time
90
     * @param {object} options
91
     * @param {string} options.apiUrl
92
     * @param {string} options.apiKey
93
     * @param {string} options.platformName
94
     * @param {string} options.platformVersion
95
     * @private
96
     */
97
    __initialize(options) {
98
        let that = this;
99
        // Run itself as a self invoked promise that is awaited by nothing. callMethod shall wait until this is finished
100
        (async function __initialize_self() {
101
            let error = null;
102
            let result = null;
103
            let regex_url = /^(?:([A-Za-z]+):)?(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/;
104
            let url_parts = regex_url.exec(options.apiUrl);
105
            that.defaultVars.baseUrl = that.defaultVars.baseUrl || url_parts[1] + ':' + url_parts[2] + url_parts[3] + '/';
106
            that.defaultVars.apiUrl = that.defaultVars.apiUrl || options.apiUrl;
107
            that.defaultVars.apiKey = that.defaultVars.apiKey || options.apiKey;
108
            that.defaultVars.uniqueId = that.defaultVars.uniqueId || md5(that.defaultVars.clientName + that.defaultVars.clientVersion + options.platformName + options.platformVersion + that.constructor.getMacAddress() + new Date().getTime());
109
110
            try {
111
                /**
112
                 *
113
                 * @type {{}}
114
                 * @property {string} apiversion
115
                 * @property {string} apiaccesstoken
116
                 * @property {string} sessionhash
117
                 * @property {string} apiclientid
118
                 * @property {string} secret
119
                 */
120
                let response = await that.callMethod({
121
                    method: 'api_init',
122
                    params: {
123
                        clientname: that.defaultVars.clientName,
124
                        clientversion: that.defaultVars.clientVersion,
125
                        platformname: options.platformName,
126
                        platformversion: options.platformVersion,
127
                        uniqueid: that.defaultVars.uniqueId
128
                    }
129
                });
130
131
                that.clientSessionVars.apiVersion = '';
132
                that.clientSessionVars.apiAccessToken = '';
133
                that.clientSessionVars.sessionHash = '';
134
                that.clientSessionVars.apiClientId = '';
135
                that.clientSessionVars.secret = '';
136
                that.clientSessionVars.inited = false;
137
                if (
138
                    response.apiversion
139
                    && response.apiaccesstoken
140
                    && response.sessionhash
141
                    && response.apiclientid
142
                    && response.secret
143
                ) {
144
                    that.clientSessionVars.apiVersion = response.apiversion;
145
                    that.clientSessionVars.apiAccessToken = response.apiaccesstoken;
146
                    that.clientSessionVars.sessionHash = response.sessionhash;
147
                    that.clientSessionVars.apiClientId = response.apiclientid;
148
                    that.clientSessionVars.secret = response.secret;
149
                    that.clientSessionVars.inited = true;
150
                    that.__waitingForInitializationCallback(true);
151
                    result = that;
152
                }
153
154
                if (result === null) {
155
                    that.clientSessionVars.error = that.constructor.parseErrorMessage(response) || 'TODO ERROR (api connection did not return a session)';
156
                    that.__waitingForInitializationCallback(false);
157
                    error = that.clientSessionVars.error;
158
                }
159
            } catch (e) {
160
                that.clientSessionVars.error = e;
161
                that.__waitingForInitializationCallback(false);
162
                // reject(e);
163
                error = e;
164
            }
165
            return error || result;
166
        }());
167
    }
168
169
    /**
170
     * Will return after #initialize() is complete. Otherwise may reject() after 15 second timeout
171
     * @param {number=15} waitTime
172
     * @returns {Promise<void>}
173
     * @fulfill {void}
174
     * @reject {string} - Error Reason
175
     */
176
    async waitForInitialization(waitTime) {
177
        let that = this;
178
        waitTime = waitTime || 15;
179
        return new Promise(async function (resolve, reject) {
180
            if (that.clientSessionVars.inited === true) {
181
                resolve();
182
            } else if (that.clientSessionVars.error !== null) {
183
                reject(that.clientSessionVars.error);
184
            }
185
186
            /**
187
             * @type {number}
188
             * @private
189
             */
190
            that.__waitingForInitializationTimeout = setTimeout(
191
                function () {
192
                    that.__waitingForInitializationCallback = function () {
193
                    }; // Set back to a blank function
194
                    if (that.clientSessionVars.inited === true) {
195
                        resolve();
196
                    } else {
197
                        reject('Connection could not be achieved due to timed out', that.clientSessionVars.error);
198
                    }
199
200
                },
201
                waitTime * 1000 // 1 minute timeout
202
            );
203
            /**
204
             * @param {boolean=true} success
205
             * @private
206
             */
207
            that.__waitingForInitializationCallback = function (success) {
208
                if (that.__waitingForInitializationTimeout) {
209
                    clearTimeout(that.__waitingForInitializationTimeout);
210
                }
211
                if (success === false) {
212
                    reject(that.clientSessionVars.error);
213
                } else {
214
                    resolve();
215
                }
216
            };
217
        })
218
    }
219
220
    /**
221
     *
222
     * @param {object} options
223
     * @param {string} options.method - Required action to take
224
     * @param {object<string,string>} [options.params={}] - Optional parameter variables
225
     * @param {?object<string,string>} [options.cookies] - Optional cookie variables
226
     * @returns {Promise<{}>}
227
     * @fulfill {{}}
228
     * @reject {string} - Error Reason
229
     */
230
    async callMethod(options) {
231
        let that = this;
232
        let sign = true;
233
        options = options || {};
234
        options.params = options.params || {};
235
        return new Promise(async function (resolve, reject) {
236
            try {
237
                if (!options.method) {
238
                    reject('callMethod(): requires a supplied method');
239
                    return;
240
                }
241
242
                // Sign all calls except for api_init
243
                if (options.method === 'api_init') {
244
                    sign = false;
245
                }
246
247
                // await a valid session before continuing (skipping waiting on __initialize())
248
                if (sign === true) {
249
                    await that.waitForInitialization();
250
                }
251
252
                // Gather our sessions variables together
253
                let reqParams = {
254
                    api_m: options.method,
255
                    api_c: that.clientSessionVars.apiClientId, //clientId
256
                    api_s: that.clientSessionVars.apiAccessToken, //apiAccessToken (may be empty)
257
                    api_v: that.clientSessionVars.apiVersion //api version
258
                };
259
                _.extend(reqParams, options.params); // Combine the arrays
260
261
                if (sign === true) {
262
                    // Generate a signature to validate that we are authenticated
263
                    if (that.clientSessionVars.inited) {
264
                        reqParams.api_sig = md5(that.clientSessionVars.apiAccessToken + that.clientSessionVars.apiClientId + that.clientSessionVars.secret + that.defaultVars.apiKey);
265
                    } else {
266
                        reject('callMethod(): requires initialization. Not initialized');
267
                        return;
268
                    }
269
                }
270
271
                // Create a valid http Request
272
                let reqOptions = {
273
                    url: that.defaultVars.apiUrl,
274
                    formData: reqParams,
275
                    headers: {
276
                        'User-Agent': that.defaultVars.clientName
277
                    }
278
                };
279
280
                // Some command require adding a cookie, we'll do that here
281
                if (options.cookies) {
282
                    let j = request.jar();
283
                    for (let variable in options.cookies) {
284
                        if (options.cookies.hasOwnProperty(variable)) {
285
                            let cookieString = variable + '=' + options.cookies[variable];
286
                            let cookie = request.cookie(cookieString);
287
                            j.setCookie(cookie, that.defaultVars.baseUrl);
288
                        }
289
                    }
290
                    reqOptions.jar = j;// Adds cookies to the request
291
                }
292
293
                request.post(
294
                    reqOptions,
295
                    function (error, response, body) {
296
                        if (!error && response.statusCode === 200) {
297
                            resolve(JSON.parse(body));
298
                        } else {
299
                            //console.log('No response');
300
                            reject('callMethod(): no response.');
301
                        }
302
                    }
303
                );
304
            } catch (e) {
305
                reject(e);
306
            }
307
        });
308
    }
309
310
    /**
311
     * Attempts to log in a user.
312
     * @param {string} username - Username
313
     * @param {string} password - clear text password TODO need to secure this more?
314
     * @param {object=} options
315
     * @param {string=} options.username - Ignore, already required at username
316
     * @param {string=} options.password - Ignore, already required at password
317
     * @returns {Promise<UserVars>}
318
     * @fulfill {UserVars}
319
     * @reject {string} - Error Reason. Expects: (TODO list common errors here)
320
     */
321
    async login(username, password, options) {
322
        options = options || {};
323
        options.username = username || options.username || '';
324
        options.password = md5(password || options.password || '');
325
        return await this.loginMD5('', '', options);
326
    }
327
328
    /**
329
     *
330
     * Attempts to log in a user. Requires the password to be pre md5 hashed.
331
     * @param {string} username - Username
332
     * @param {string} password - MD5 hashed password TODO need to secure this more?
333
     * @param {object=} options
334
     * @param {string=} options.username - Ignore, already required at username
335
     * @param {string=} options.password - Ignore, already required at password
336
     * @returns {Promise<UserVars>}
337
     * @fulfill {UserVars}
338
     * @reject {string} - Error Reason. Expects: (TODO list common errors here)
339
     */
340
    async loginMD5(username, password, options) {
341
        let that = this;
342
        options = options || {};
343
        options.username = username || options.username || {};
344
        options.password = password || options.password || {};
345
        return new Promise(async function (resolve, reject) {
346
            try {
347
                let response = await that.callMethod(
348
                    {
349
                        method: 'login_login',
350
                        params: {
351
                            vb_login_username: options.username || '',
352
                            vb_login_md5password: options.password || ''
353
                        }
354
                    }
355
                );
356
                /**
357
                 redirect_login - (NOT A ERROR) Login successful
358
                 badlogin - Username or Password incorrect. Login failed.
359
                 badlogin_strikes - Username or Password incorrect. Login failed. You have used {X} out of 5 login attempts. After all 5 have been used, you will be unable to login for 15 minutes.
360
                 */
361
                let error = that.constructor.parseErrorMessage(response);
362
                if (response.session) {
363
                    that.userSessionVars = response.session;
364
                    if (error === 'redirect_login') {
365
                        that.userSessionVars.username = options.username;
366
                        that.userSessionVars.loggedIn = true;
367
                    }
368
                }
369
                if (error === 'redirect_login') {
370
                    error = null;
371
                }
372
                if (error === null) {
373
                    resolve(that.userSessionVars);
374
                } else {
375
                    reject(error);
376
                }
377
378
            } catch (e) {
379
                reject(e);
380
            }
381
        });
382
    }
383
384
    /**
385
     * Attempts to log the user out.
386
     * @returns {Promise<boolean>} - Returns true on success, otherwise error code is rejected
387
     * @fulfill {boolean}
388
     * @reject {string} - Error Reason
389
     */
390
    async logout() {
391
        let that = this;
392
        return new Promise(async function (resolve, reject) {
393
            let error;
394
            try {
395
                let response = await that.callMethod({
396
                    method: 'login_logout'
397
                });
398
                error = that.constructor.parseErrorMessage(response);
399
                if (response.session) {
400
                    that.userSessionVars = response.session;
401
                    if (error === 'cookieclear') {
402
                        that.userSessionVars.username = '';
403
                        that.userSessionVars.loggedIn = false;
404
                    }
405
                }
406
                if (error === 'cookieclear') {
407
                    error = null;
408
                }
409
            } catch (e) {
410
                reject(e);
411
            }
412
413
            if (error) {
414
                reject(error);
415
            } else {
416
                resolve(true)
417
            }
418
        });
419
    }
420
421
    /**
422
     * Return a Mac address of a network interface for machine identification
423
     * @returns {string} macAddress
424
     */
425
    static getMacAddress() {
426
        let interfaces = os.networkInterfaces();
427
        let address = '';
428
        loop1:
429
            for (let k in interfaces) {
430
                if (interfaces.hasOwnProperty(k)) {
431
                    for (let k2 in interfaces[k]) {
432
                        if (interfaces[k].hasOwnProperty(k2)) {
433
                            let addressI = interfaces[k][k2];
434
                            if (
435
                                (addressI.family === 'IPv4' || addressI.family === 'IPv6')
436
                                && addressI.hasOwnProperty('internal')
437
                                && addressI.internal === false
438
                                && addressI.hasOwnProperty('mac')
439
                                && addressI.mac !== '00:00:00:00:00:00'
440
                            ) {
441
                                address = addressI.mac;
442
                                break loop1;
443
                            }
444
                        }
445
                    }
446
                }
447
            }
448
        return address;
449
    }
450
451
    /**
452
     *
453
     * @param {object} response - Response object from callMethod()
454
     * @returns {string} status - Error message
455
     */
456
    static parseErrorMessage(response) {
457
        let retur = '';
458
        if (
459
            response.hasOwnProperty('response')
460
            && response.response.hasOwnProperty('errormessage')
461
        ) {
462
            if (_.isArray(response.response.errormessage)) {
463
                retur = response.response.errormessage[0]
464
            } else {
465
                retur = response.response.errormessage;
466
            }
467
        }
468
        return retur;
469
    }
470
471
    /**
472
     * List every Forum and sub forum available to the user.
473
     * @returns {Promise<Forum[]>} - Array of Forum objects
474
     * @fulfill {Forum[]}
475
     * @reject {string} - Error Reason. Expects: (TODO list common errors here)
476
     */
477
    async getForums() {
478
        let that = this;
479
        return new Promise(async function (resolve, reject) {
480
            let forums = [];
481
            try {
482
                let response = await that.callMethod(
483
                    {
484
                        method: 'api_forumlist'
485
                    });
486
487
                if (response) {
488
                    for (let forum in response) {
489
                        if (response.hasOwnProperty(forum)) {
490
                            forums.push(new Forum(response[forum]));
491
                        }
492
                    }
493
                }
494
            } catch (e) {
495
                reject(e);
496
            }
497
            resolve(forums);
498
        });
499
    }
500
501
    /**
502
     * List detailed info about a forum and it's sub-forums and threads
503
     * @param {number} forumId - Forum id
504
     * @param {object=} options - Secondary Options
505
     * @param {number=} options.forumid - Ignore, already required at forumId
506
     * TODO note additional options
507
     * @returns {Promise<Forum>} - Returns a Forum object
508
     * @fulfill {Forum}
509
     * @reject {string} - Error Reason. Expects: (TODO list common errors here)
510
     */
511
    async getForum(forumId, options) {
512
        let that = this;
513
        options = options || {};
514
        options.forumid = forumId || options.forumid || ''; //required
515
516
        return new Promise(async function (resolve, reject) {
517
            let forum = null;
518
            try {
519
                let response = await that.callMethod({
520
                    method: 'forumdisplay',
521
                    params: options
522
                });
523
                if (
524
                    response
525
                    && response.hasOwnProperty('response')
526
                ) {
527
                    forum = new Forum(response.response);
528
                }
529
            } catch (e) {
530
                reject(e);
531
            }
532
            if (forum !== null) {
533
                resolve(forum);
534
            } else {
535
                reject();
536
            }
537
        });
538
    }
539
540
    /**
541
     * List detailed information about a Thread and it's Posts
542
     * @param {number} threadId - Thread id
543
     * @param {object=} options - Secondary Options
544
     * @param {number=} options.threadid - Ignore, already required at threadId
545
     * TODO note additional options
546
     * @returns {Promise<Thread>} - Returns a Thread object
547
     * @fulfill {Thread}
548
     * @reject {string} - Error Reason. Expects: (TODO list common errors here)
549
     */
550
    async getThread(threadId, options) {
551
        let that = this;
552
        options = options || {};
553
        options.threadid = threadId || options.threadid || ''; //required
554
555
        return new Promise(async function (resolve, reject) {
556
            let thread = null;
557
            try {
558
                let response = await that.callMethod({
559
                    method: 'showthread',
560
                    params: options
561
                });
562
                if (
563
                    response
564
                    && response.hasOwnProperty('response')
565
                ) {
566
                    thread = new Thread(response.response);
567
                }
568
            } catch (e) {
569
                reject(e);
570
            }
571
            if (thread !== null) {
572
                resolve(thread);
573
            } else {
574
                reject();
575
            }
576
        });
577
    }
578
579
    /**
580
     * Attempts to submit a new Post into a specified Thread
581
     * @param {number} threadId - Thread id
582
     * @param {string} message - Post Message
583
     * @param {object=} options
584
     * @param {boolean=} options.signature  - Optionally append your signature
585
     * @param {number=} options.threadid - Ignore, already required at threadId
586
     * @param {string=} options.message - Ignore, already required at message
587
     * TODO note additional options
588
     * @returns {Promise<*>} - Returns a unhandled response currently
589
     * @fulfill {*}
590
     * @reject {string} - Error Reason. Expects: (TODO list common errors here)
591
     */
592
    async newPost(threadId, message, options) {
593
        let that = this;
594
        options = options || {};
595
        options.threadid = threadId || options.threadid || ''; //required
596
        options.message = message || options.message || ''; //required
597
        if (options.signature === true) {
598
            //System only handle 1 or 0. defaults to 0
599
            options.signature = '1';
600
        }
601
602
        return new Promise(async function (resolve, reject) {
603
            try {
604
                let response = await that.callMethod({
605
                    method: 'newreply_postreply',
606
                    params: options
607
                });
608
                let possibleError = that.constructor.parseErrorMessage(response);
609
                //success is errormessgae 'redirect_postthanks'
610
                //error 'threadclosed' if thread is closed. FIXME does not error
611
                //reports threadid and postid
612
                if (
613
                    possibleError === 'redirect_postthanks'
614
                    && response.hasOwnProperty('show')
615
                ) {
616
                    resolve(response.show);
617
                } else {
618
                    reject(possibleError || response);
619
                }
620
            } catch (e) {
621
                reject(e);
622
            }
623
        });
624
    }
625
626
    /**
627
     * Attempts to edit an existing Post
628
     * @param {number} postId - Post id
629
     * @param {string} message - Post Message
630
     * @param {object=} options
631
     * @param {string=} options.reason - Reason for editing
632
     * @param {boolean=} options.signature - Optionally append your signature
633
     * @param {number=} options.postid - Ignore, already required at postId
634
     * @param {string=} options.message - Ignore, already required at message
635
     * TODO note additional options
636
     * @returns {Promise<*>} - Returns a unhandled response currently
637
     * @fulfill {*}
638
     * @reject {string} - Error Reason. Expects: (TODO list common errors here)
639
     */
640
    async editPost(postId, message, options) {
641
        let that = this;
642
        options = options || {};
643
        options.postid = postId || options.postid || ''; //required
644
        options.message = message || options.message || ''; //required
645
        if (options.signature === true) {
646
            //System only handle 1 or 0. defaults to 0
647
            options.signature = '1';
648
        }
649
650
        return new Promise(async function (resolve, reject) {
651
            try {
652
                let response = await that.callMethod({
653
                    method: 'editpost_updatepost',
654
                    params: options
655
                });
656
                let possibleError = that.constructor.parseErrorMessage(response);
657
                //success is errormessgae 'redirect_editthanks'
658
                if (possibleError === 'redirect_editthanks') {
659
                    resolve({postid: options.postid});
660
                } else {
661
                    reject(possibleError || response);
662
                }
663
            } catch (e) {
664
                reject(e);
665
            }
666
        });
667
    }
668
669
    /**
670
     * Attempts to retrieve data about a specific user found by username
671
     * @param {string} username - Username
672
     * @param {object=} options - Secondary Options
673
     * @param {string=} options.username - Ignore, already required at username
674
     * @returns {Promise<Member>} - Returns a Member object
675
     * @fulfill {Member}
676
     * @reject {string} - Error Reason. Expects: (TODO list common errors here)
677
     */
678
    async getMember(username, options) {
679
        let that = this;
680
        options = options || {};
681
        options.username = username || options.username || ''; //required
682
683
        return new Promise(async function (resolve, reject) {
684
            let thread = null;
685
            try {
686
                let response = await that.callMethod({
687
                    method: 'member',
688
                    params: options
689
                });
690
                if (
691
                    response
692
                    && response.hasOwnProperty('response')
693
                ) {
694
                    thread = new Member(response.response);
695
                }
696
            } catch (e) {
697
                reject(e);
698
            }
699
            if (thread !== null) {
700
                resolve(thread);
701
            } else {
702
                reject();
703
            }
704
        });
705
    }
706
707
    /**
708
     * TODO untested - does not seem to function yet
709
     * Attempts to delete an existing Post
710
     * @param {number} postId - Post id
711
     * @param {number} threadId - Thread id
712
     * @param {object=} options
713
     * @param {string=} options.reason - Reason for deleting
714
     * @param {number=} options.postid - Ignore, already required at postId
715
     * @param {number=} options.threadid - Ignore, already required at threadId
716
     * TODO note additional options
717
     * @returns {Promise<*>} - Returns a unhandled response currently
718
     * @fulfill {*}
719
     * @reject {string} - Error Reason. Expects: (TODO list common errors here)
720
     */
721
    async deletePost(postId, threadId, options) {
722
        let that = this;
723
        options = options || {};
724
        options.postid = postId || options.postid || ''; //required
725
        options.threadid = threadId || options.threadid || ''; // TODO required????
726
727
        return new Promise(async function (resolve, reject) {
728
            try {
729
                let response = await that.callMethod({
730
                    method: 'editpost_deletepost',
731
                    params: options
732
                });
733
                let possibleError = that.constructor.parseErrorMessage(response);
734
                //unknown response
735
                if (
736
                    possibleError === 'redirect_deletepost'
737
                    && response.hasOwnProperty('show')
738
                ) {
739
                    //console.log('response', response);
740
                    resolve(response.show);
741
                } else {
742
                    reject(possibleError || response);
743
                }
744
            } catch (e) {
745
                reject(e);
746
            }
747
        });
748
    }
749
750
    /**
751
     * Attempts to submit a new Thread into a specified Forum. This will also be considered the first Post
752
     * @param {number} forumId - Forum Id
753
     * @param {string} subject - Post/Thread Subject
754
     * @param {string} message - Post Message
755
     * @param {object=} options
756
     * @param {boolean=} options.signature - Optionally append your signature
757
     * @param {number=} options.forumid - Ignore, already required at postId
758
     * @param {string=} options.subject - Ignore, already required at postId
759
     * @param {string=} options.message - Ignore, already required at postId
760
     * TODO note additional options
761
     * @returns {Promise<*>} - Returns a unhandled response currently
762
     * @fulfill {*}
763
     * @reject {string} - Error Reason. Expects: (TODO list common errors here)
764
     */
765
    async newThread(forumId, subject, message, options) {
766
        let that = this;
767
        options = options || {};
768
        options.forumid = forumId || options.forumid || ''; //required
769
        options.subject = subject || options.subject || ''; //required
770
        options.message = message || options.message || ''; //required
771
772
        if (options.signature === true) {
773
            //System only handle 1 or 0. defaults to 0
774
            options.signature = '1'; // FIXME This didn't seem to work
775
        }
776
777
        return new Promise(async function (resolve, reject) {
778
            try {
779
                let response = await that.callMethod({
780
                    method: 'newthread_postthread',
781
                    params: options
782
                });
783
                let possibleError = that.constructor.parseErrorMessage(response);
784
                //success is errormessgae 'redirect_postthanks'
785
                //reports threadid and postid
786
                if (
787
                    possibleError === 'redirect_postthanks'
788
                    && response.hasOwnProperty('show')
789
                ) {
790
                    resolve(response.show);
791
                } else {
792
                    reject(possibleError || response);
793
                }
794
            } catch (e) {
795
                reject(e);
796
            }
797
        });
798
    }
799
800
    /**
801
     * Get logged in user's Inbox and list of private Messages
802
     * @param {object=} options
803
     * @returns {Promise<Inbox>} - Returns an Inbox object
804
     * @fulfill {Inbox}
805
     * @reject {string} - Error Reason. Expects: (TODO list common errors here)
806
     */
807
    async getInbox(options) {
808
        let that = this;
809
        options = options || {};
810
811
        return new Promise(async function (resolve, reject) {
812
            let inbox = null;
813
            try {
814
                let response = await that.callMethod({
815
                    method: 'private_messagelist',
816
                    params: options
817
                });
818
                if (
819
                    response
820
                    && response.hasOwnProperty('response')
821
                ) {
822
                    inbox = new Inbox(response.response);
823
                }
824
            } catch (e) {
825
                reject(e);
826
            }
827
            if (inbox !== null) {
828
                resolve(inbox);
829
            } else {
830
                reject();
831
            }
832
        });
833
    }
834
835
    /**
836
     * Get details of a specific Message for the logged in user
837
     * @param {number} id
838
     * @param {object=} options
839
     * @param {number=} options.pmid - Ignore, already required at id
840
     * @returns {Promise<Message>} - Returns a Message object
841
     * @fulfill {Message}
842
     * @reject {string} - Error Reason. Expects: (TODO list common errors here)
843
     */
844
    async getMessage(id, options) {
845
        let that = this;
846
        options = options || {};
847
        options.pmid = id || options.pmid || ''; //required
848
849
        return new Promise(async function (resolve, reject) {
850
            let message = null;
851
            try {
852
                let response = await that.callMethod({
853
                    method: 'private_showpm',
854
                    params: options
855
                });
856
                if (
857
                    response
858
                    && response.hasOwnProperty('response')
859
                ) {
860
                    message = new Message(response.response);
861
                }
862
            } catch (e) {
863
                reject(e);
864
            }
865
            if (message !== null) {
866
                resolve(message);
867
            } else {
868
                reject();
869
            }
870
        });
871
    }
872
873
    /**
874
     *
875
     * @param {string} username - Username to send the message to
876
     * @param {string} title - Message Subject
877
     * @param {string} message - Message content
878
     * @param {object=} options
879
     * @param {boolean=} options.signature - Optionally append your signature
880
     * @param {string=} options.recipients - Ignore, already required at username
881
     * @param {string=} options.title - Ignore, already required at title
882
     * @param {string=} options.message - Ignore, already required at message
883
     * TODO note additional options
884
     * @returns {Promise<Void>} - Successfully completes if sent. TODO: provide a better response
885
     * @fulfill {Void}
886
     * @reject {string} - Error Reason. Expects: (TODO list common errors here)
887
     */
888
    async sendMessage(username, title, message, options) {
889
        let that = this;
890
        options = options || {};
891
        options.recipients = username || options.recipients || ''; //required
892
        options.title = title || options.title || ''; //required
893
        options.message = message || options.message || ''; //required
894
        options.signature = options.signature === true ? '1' : '0';
895
896
        return new Promise(async function (resolve, reject) {
897
            try {
898
                let response = await that.callMethod({
899
                    method: 'private_insertpm',
900
                    params: options
901
                });
902
                let possibleError = that.constructor.parseErrorMessage(response);
903
                if (possibleError !== 'pm_messagesent') {
904
                    reject(possibleError || response);
905
                }
906
            } catch (e) {
907
                reject(e);
908
            }
909
            resolve();
910
        });
911
    }
912
913
    /**
914
     * TODO incomplete - does not seem to function yet
915
     * Attempts to close a specific Thread. Requires a user to have a 'inline mod' permissions
916
     * @param {number} threadId - Id of Thread to close
917
     * @returns {Promise<*>} - Returns a unhandled response currently
918
     * @fulfill {*}
919
     * @reject {string} - Error Reason. Expects: (TODO list common errors here)
920
     */
921
    async modCloseThread(threadId) {
922
        let that = this;
923
        let cookies = {};
924
        if (threadId) {
925
            //TODO multiple ids are delimited with a '-'. eg: 123-345-456
926
            cookies.vbulletin_inlinethread = threadId;
927
        }
928
        return new Promise(async function (resolve, reject) {
929
            try {
930
                let response = await that.callMethod({
931
                    method: 'inlinemod_close',
932
                    cookies: cookies || {}
933
                });
934
                //let possibleError = that.constructor.parseErrorMessage(response);
935
                //unknown responses
936
                /*if (
937
                    possibleError === 'redirect_postthanks'
938
                    && response.hasOwnProperty('show')
939
                ) {*/
940
                resolve(response);
941
                /*} else {
942
                    reject(possibleError || response);
943
                }*/
944
            } catch (e) {
945
                reject(e);
946
            }
947
        });
948
    }
949
950
    /**
951
     * TODO incomplete - does not seem to function yet
952
     * Attempts to open a specific Thread. Requires a user to have a 'inline mod' permissions
953
     * @param {number} threadId - Id of Thread to open
954
     * @returns {Promise<*>} - Returns a unhandled response currently
955
     * @fulfill {*}
956
     * @reject {string} - Error Reason. Expects: (TODO list common errors here)
957
     */
958
    async modOpenThread(threadId) {
959
        let that = this;
960
        let cookies = {};
961
        if (threadId) {
962
            //TODO multiple ids are delimited with a '-'. eg: 123-345-456
963
            cookies.vbulletin_inlinethread = threadId;
964
        }
965
        return new Promise(async function (resolve, reject) {
966
            try {
967
                let response = await that.callMethod({
968
                    method: 'inlinemod_open',
969
                    cookies: cookies || {}
970
                });
971
                //let possibleError = that.constructor.parseErrorMessage(response);
972
                //unknown responses
973
                /*if (
974
                    possibleError === 'redirect_postthanks'
975
                    && response.hasOwnProperty('show')
976
                ) {*/
977
                resolve(response);
978
                /*} else {
979
                    reject(possibleError || response);
980
                }*/
981
            } catch (e) {
982
                reject(e);
983
            }
984
        });
985
    }
986
987
    /**
988
     * TODO incomplete - does not seem to function yet
989
     * Attempts to delete a specific Thread. Requires a user to have a 'inline mod' permissions
990
     * @param {number} threadId - Id of Thread to close
991
     * @returns {Promise<*>} - Returns a unhandled response currently
992
     * @fulfill {*}
993
     * @reject {string} - Error Reason. Expects: (TODO list common errors here)
994
     */
995
    async modDeleteThread(threadId) {
996
        let that = this;
997
        let cookies = {};
998
        if (threadId) {
999
            //TODO multiple ids are delimited with a '-'. eg: 123-345-456
1000
            cookies.vbulletin_inlinethread = threadId;
1001
        }
1002
        return new Promise(async function (resolve, reject) {
1003
            try {
1004
                let response = await that.callMethod({
1005
                    method: 'inlinemod_dodeletethreads',
1006
                    cookies: cookies || {}
1007
                });
1008
                //let possibleError = that.constructor.parseErrorMessage(response);
1009
                //unknown responses
1010
                /*if (
1011
                    possibleError === 'redirect_postthanks'
1012
                    && response.hasOwnProperty('show')
1013
                ) {*/
1014
                resolve(response);
1015
                /*} else {
1016
                    reject(possibleError || response);
1017
                }*/
1018
            } catch (e) {
1019
                reject(e);
1020
            }
1021
        });
1022
    }
1023
}
1024
1025
module.exports = VBApi;
1026